//因为要包含头文件，所以需要防止头文件的重复包含，可以使用宏来控制
#ifndef __M_UTIL_H__
#define __M_UTIL_H__

/*
    实用工具类的实现：
    1. 获取系统时间
    2. 判断文件是否存在
    3. 获取文件所在路径
    4. 创建目录
*/

#include <iostream>
#include <ctime>
#include <sys/stat.h>
#include <memory>
#include <execinfo.h>
#include <sstream>

namespace superlog
{
    namespace util
    {
        class Date
        {
        public:
            // 获取系统时间
            static time_t now() //定义成静态接口的好处：无需实例化对象，直接通过类名和作用域限定符即可直接访问接口
            {
                return time(nullptr);
            }
        };

        class File
        {
        public:
            // 判断文件是否存在
            static bool exists(const std::string& pathname)
            {
                struct stat st;
                return (stat(pathname.c_str(), &st) == 0);
            }
            
            // 获取文件所在路径(不包含文件名)
            static std::string path(const std::string& pathname)
            {
                // ./abc/test.txt
                size_t pos = pathname.find_last_of("/\\");
                if(pos == std::string::npos) return ".";
                return pathname.substr(0, pos+1);
            }

            // 创建目录
            static void createDirectory(const std::string& pathname)
            {
                // ./abc/xyz/test.txt
                size_t pos = 0; //查找斜杠('/' '\') 的位置
                size_t idx = 0; //记录要查找的起始位置
                while(idx < pathname.size())
                {
                    pos = pathname.find_first_of("/\\", idx);
                    if(pos == std::string::npos)
                    {
                        mkdir(pathname.c_str(), 0777); //0777说明谁都可以访问
                    }
                    std::string parent_dir = pathname.substr(0, pos+1); //要特别注意这里起始位置和长度(+1说明包括斜杠)
                    
                    if(exists(parent_dir))
                    {
                        idx = pos + 1;
                        continue;
                    }
                    mkdir(parent_dir.c_str(), 0777);
                    idx = pos + 1;
                }
            }
        };

        // 使用Tracer类需要确保程序是使用调试符号-g编译的
        class Tracer
        {
        public:
            static std::string getFileLine(void* addr)
            {
                char addr_str[100]; //存储地址的字符串表示形式
                sprintf(addr_str, "%p", addr); //将addr格式化为字符串，并存储在addr_str中

                // 构建addr2line的命令行(将程序中的地址转换为源代码文件和行号)
                std::string cmd = "addr2line -e ./test " + std::string(addr_str); //-e指定了要使用的可执行文件(可更改)
                
                FILE* pipe = popen(cmd.c_str(), "r"); //popen可以启动一个新的进程来运行指定的命令
                if (!pipe)
                {
                    return "[ERROR] addr2line failed";
                }
                char buffer[128];
                std::string result;
                while (fgets(buffer, sizeof(buffer), pipe) != NULL) //从管道中读取数据到buffer数组中
                {
                    result += buffer;
                }
                pclose(pipe);

                std::stringstream ss(result);
                std::string filename; //文件名
                std::string line; //行号
                std::getline(ss, filename, ':'); //提取到:之前的部分
                std::getline(ss, line); //提取剩下的部分
                return filename + ":" + line;
            }

            // 获取当前调用堆栈信息
            static std::string stackTrace()
            {
                std::unique_ptr<void*[]> trace(new void*[32]{0}); //存储堆栈帧的地址
                int n = backtrace(trace.get(), 32); //获取堆栈帧的数量
                char** msgs = backtrace_symbols(trace.get(), n); //将地址转换为对应栈帧符号信息字符串
                std::stringstream ss;
                for(int i = 0; i < n; i++)
                {
                    std::string fileline = getFileLine(trace.get()[i]);
                    ss << i + 1 << " [stacktrace] " << "[" << trace.get()[i] << "] " << fileline;
                    if(i < n - 1)
                    {
                        ss << std::endl;
                    }
                }
                free(msgs);
                return ss.str();
            }
        };
    }
}

#endif